/**
 *
 * @param {*} str
 * @returns {Array<{
 *  name: string,
 *  args: Array<{
 *      value: string | number,
 *      dynamic: boolean,
 *  }>,
 * }>}}
 */
function parseDirective(str) {
    function curr() {
        return str.charCodeAt(index);
    }

    function next() {
        return str.charCodeAt(++index);
    }

    function eof() {
        return index >= len;
    }

    function eatSpace() {
        while (curr() === spaceChar) {
            next();
        }
    }

    function parseString() {
        var stringQuote = curr();
        var chr;
        while (!eof()) {
            chr = next();
            // escape char
            if (chr === escapeChr) {
                next();
            } else if (chr === stringQuote) {
                break;
            }
        }
        next();
    }

    var reservedArgRE = /^in$|^-?\d+/;
    /**
     * Check and convert possible numeric strings to numbers
     * before setting back to data
     *
     * @param {*} value
     * @return {*|Number}
     */

    function toNumber(value) {
        if (typeof value !== "string") {
            return value;
        } else {
            var parsed = Number(value);
            return Number.isNaN(parsed) ? value : parsed;
        }
    }

    /**
     * Strip quotes from a string
     *
     * @param {String} str
     * @return {String | false}
     */

    function stripQuotes(str) {
        var a = str.charCodeAt(0);
        var b = str.charCodeAt(str.length - 1);
        return a === b && (a === 0x22 || a === 0x27) ? str.slice(1, -1) : str;
    }
    /**
     * Check if an argument is dynamic and strip quotes.
     *
     * @param {String} arg
     * @return {Object}
     */

    function processFilterArg(arg) {
        if (reservedArgRE.test(arg)) {
            return {
                value: toNumber(arg),
                dynamic: false,
            };
        } else {
            var stripped = stripQuotes(arg);
            var dynamic = stripped === arg;
            value = unescapeString(stripped);
            return {
                value: value,
                dynamic: dynamic,
            };
        }
    }

    function unescapeString(str) {
        return str.replace(/\\\\/g, "\\").replace(/\\"/g, '"');
    }

    function pushFilterArg() {
        filters[filters.length - 1].args.push(
            processFilterArg(str.substring(start, index)),
        );
    }

    function pushFilterName() {
        filters.push({
            name: str.substring(start, index),
            args: [],
        });
    }

    let index = 0,
        len = str.length;
    const spaceChar = " ".charCodeAt(0);
    const pipeChar = "|".charCodeAt(0);
    const doubleQuoteChar = '"'.charCodeAt(0);
    const singleQuoteChar = "'".charCodeAt(0);
    var escapeChr = 0x5c;
    const commaChar = ",".charCodeAt(0);
    const parenthesisBeg = "(".charCodeAt(0);
    const parenthesisEnd = ")".charCodeAt(0);
    const filters = [];
    const FILTER_NAME = 1;
    const FILTER_ARGS = 2;
    let state = FILTER_NAME;
    var start = 0;
    while (!eof()) {
        const char = curr();
        if (state === FILTER_NAME) {
            if (char === pipeChar || char === spaceChar) {
                if (start !== index) {
                    pushFilterName();
                }
                next();
                eatSpace();
                start = index;
            } else if (char === parenthesisBeg) {
                if (start !== index) {
                    pushFilterName();
                }
                next();
                eatSpace();
                start = index;
                state = FILTER_ARGS;
            } else {
                next();
            }
        } else if (state === FILTER_ARGS) {
            if (char === pipeChar || char === spaceChar || char === commaChar) {
                if (start !== index) {
                    pushFilterArg();
                }
                next();
                eatSpace();
                start = index;
            } else if (char === doubleQuoteChar || char === singleQuoteChar) {
                parseString();
                pushFilterArg();
                eatSpace();
                start = index;
            } else if (char === parenthesisEnd) {
                if (start !== index) {
                    pushFilterArg();
                }
                next();
                eatSpace();
                start = index;
                state = FILTER_NAME;
            } else {
                next();
            }
        } else {
            next();
        }
    }
    if (start !== index) {
        pushFilterName();
    }
    return filters;
}

module.exports = {
    parseDirective,
};
